package com.simen.warnview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
 * 自定义消息角标
 */
public class WarnView extends ImageView {

    public final static int GRAVITY_LEFT = 0x03;
    public final static int GRAVITY_RIGHT = 0x05;
    public final static int GRAVITY_TOP = 0x30;
    public final static int GRAVITY_BOTTOM = 0x50;

    private static final int DEFAULT_GRAVITY = GRAVITY_LEFT | GRAVITY_TOP;

    //角标
    private Drawable mWarnIcon = null;
    private Rect mPaddingRect = null;
    private Rect mCropRect = null;
    private int gravity = DEFAULT_GRAVITY;

    private Formatter mFormatter;
    private int mNumber = 0;

    private Paint mTextPaint;
    private int textColor = Color.WHITE;
    private float mTextSize = 0f;

    private boolean mDirection;
    /**
     * 脚标相对偏移距离（对角线方向，-1~0~n）
     */
    private float mSkew = 0f;

    private ValueAnimator mShakeAnimator = null;

    public interface Formatter {
        String format(int num);
    }

    public WarnView(Context context) {
        this(context, null);
    }

    public WarnView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WarnView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mTextPaint = new Paint();
        mTextPaint.setFakeBoldText(true);
        mTextPaint.setColor(textColor);

        mPaddingRect = new Rect();

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.warn);
        try {
            int paddingLeft = typedArray.getDimensionPixelSize(R.styleable.warn_warn_paddingLeft,
                    0);
            int paddingTop = typedArray.getDimensionPixelSize(R.styleable.warn_warn_paddingTop, 0);
            int paddingRight = typedArray.getDimensionPixelSize(R.styleable
                    .warn_warn_paddingRight, 0);
            int paddingBottom = typedArray.getDimensionPixelSize(R.styleable
                    .warn_warn_paddingBottom, 0);
            mPaddingRect.set(paddingLeft, paddingTop, paddingRight, paddingBottom);

            mSkew = typedArray.getFloat(R.styleable.warn_warn_skew, 0f);

            Drawable warn = typedArray.getDrawable(R.styleable.warn_warn_src);
            setWarnImage(warn, mPaddingRect);

            gravity = typedArray.getInteger(R.styleable.warn_warn_gravity, DEFAULT_GRAVITY);
            setWarnGravity(gravity);
        } finally {
            typedArray.recycle();
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        calculateRect();
    }

    /**
     * 使角标振动
     *
     * @param direction   true,横向振动;false,纵向振动
     * @param shakeValue  振幅
     * @param repeatCount 重复次数
     * @param duration    持续时间(毫秒)
     */
    public void shake(boolean direction, int shakeValue, int repeatCount, int duration) {
        if (mShakeAnimator != null && mShakeAnimator.isRunning()) {
            return;
        }
        this.mDirection = direction;
        if (shakeValue == 0) {
            return;
        }
        if (mWarnIcon == null || getWidth() == 0 || mCropRect == null) {
            return;
        }

        int start = 0;
        int end = 0;
        if (direction) {//横向震动
            start = mCropRect.left;
            end = mCropRect.left + shakeValue;
        } else {//纵向震动
            start = mCropRect.top;
            end = mCropRect.top + shakeValue;
        }
        mShakeAnimator = ValueAnimator.ofInt(start, end);
        mShakeAnimator.setDuration(duration);
        mShakeAnimator.setRepeatCount(repeatCount);
        mShakeAnimator.setRepeatMode(ValueAnimator.REVERSE);
        mShakeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                if (WarnView.this.mDirection) {
                    int width = mCropRect.width();
                    mCropRect.left = value;
                    mCropRect.right = value + width;
                } else {
                    int height = mCropRect.height();
                    mCropRect.top = value;
                    mCropRect.bottom = value + height;
                }
                invalidate();
            }
        });
        mShakeAnimator.start();
    }

    /**
     * 计算绘图区域
     */
    private void calculateRect() {
        if (mWarnIcon == null || getWidth() == 0) {
            return;
        }

        Rect warnRect = getCropRect();
        int width = warnRect.width();
        int height = warnRect.height();

        if (mSkew < -1) {
            mSkew = -1;
        }

        //计算水平填充
        switch (gravity & 0x0f) {
            case GRAVITY_RIGHT:
                warnRect.right = getWidth();
                warnRect.left = warnRect.right - width;

                warnRect.offset((int) (warnRect.width() * mSkew), 0);

                warnRect.right -= mPaddingRect.right;
                warnRect.left -= mPaddingRect.right;
                break;
            case GRAVITY_LEFT:
            default:
                warnRect.left = 0;
                warnRect.right = warnRect.left + width;

                warnRect.offset(-(int) (warnRect.width() * mSkew), 0);

                warnRect.left += mPaddingRect.left;
                warnRect.right += mPaddingRect.left;
                break;
        }

        //计算垂直填充
        switch (gravity & 0xf0) {
            case GRAVITY_BOTTOM:
                warnRect.bottom = getHeight();
                warnRect.top = warnRect.bottom - height;

                warnRect.offset(0, +(int) (warnRect.height() * mSkew));

                warnRect.bottom -= mPaddingRect.bottom;
                warnRect.top -= mPaddingRect.bottom;
                break;
            case GRAVITY_TOP:
            default:
                warnRect.top = 0;
                warnRect.bottom = warnRect.top + height;

                warnRect.offset(0, -(int) (warnRect.height() * mSkew));

                warnRect.top += mPaddingRect.top;
                warnRect.bottom += mPaddingRect.top;
                break;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final String text = getWarnText();
        if (TextUtils.isEmpty(text)) return;

        Rect warnRect = getCropRect();
        if (warnRect.width() == 0) return;

        if (mWarnIcon != null) {
            mWarnIcon.setBounds(warnRect);
            mWarnIcon.draw(canvas);
        }

        //绘制消息文本
        int width = warnRect.width();
        int height = warnRect.height();
        Paint.FontMetrics metrics = mTextPaint.getFontMetrics();
        canvas.drawText(text, warnRect.left + (width - mTextPaint.measureText(text)) / 2,
                getCenterBaseLine(warnRect.top, height, metrics), mTextPaint);
    }

    private String getWarnText() {
        if (mFormatter != null) {
            return mFormatter.format(mNumber);
        }

        if (mNumber == 0) {
            return "";
        }
        return Integer.toString(mNumber);
    }

    private Rect getCropRect() {
        if (mCropRect == null) {
            if (mWarnIcon != null) {
                mCropRect = new Rect(0, 0, mWarnIcon.getIntrinsicWidth(), mWarnIcon
                        .getIntrinsicHeight());
            } else {
                return new Rect(0, 0, 0, 0);
            }
        }
        return mCropRect;
    }

    /**
     * 将文本纵向居中绘制时,获取文本的基线坐标
     *
     * @param rectTopY    绘制区域的起始Y坐标
     * @param height      绘制区域的高度
     * @param fontMetrics
     * @return 文本的基线坐标
     */
    private float getCenterBaseLine(float rectTopY, float height, Paint.FontMetrics fontMetrics) {
        return rectTopY + (height - (fontMetrics.bottom + fontMetrics.top)) / 2;
    }

    public void setFormatter(Formatter formatter) {
        this.mFormatter = formatter;
    }

    public Formatter getFormatter() {
        return mFormatter;
    }

    /**
     * 设置消息角标
     *
     * @param drawable 角标资源id
     */
    public void setWarnImage(int drawable) {
        setWarnImage(drawable, null);
    }

    /**
     * 设置消息角标
     *
     * @param drawable 设置角标图片
     * @param padding  指定角标填充大小
     */
    public void setWarnImage(int drawable, Rect padding) {
        setWarnImage(getResources().getDrawable(drawable), padding);
    }

    private void setWarnImage(Drawable drawable, Rect padding) {
        this.mWarnIcon = drawable;
        if (padding == null) {
            padding = new Rect(0, 0, 0, 0);
        }

        this.mPaddingRect.set(padding);
        if (this.mShakeAnimator != null && this.mShakeAnimator.isRunning()) {
            this.mShakeAnimator.removeAllUpdateListeners();
            this.mShakeAnimator.cancel();
        }
        this.mCropRect = null;
        calculateRect();
        invalidate();
    }

    /**
     * 设置消息数量
     *
     * @param nums 消息数量
     */
    @Deprecated
    public void setNums(int nums) {
        this.mNumber = nums;
        invalidate();
    }

    /**
     * 设置消息数量
     *
     * @param number 消息数量
     */
    public void setNumber(int number) {
        this.mNumber = number;
        invalidate();
    }

    /**
     * 设置字体大小
     *
     * @param textSize 字体大小
     */
    public void setTextSize(float textSize) {
        this.mTextSize = textSize;
        this.mTextPaint.setTextSize(textSize);
        invalidate();
    }

    /**
     * 设置字体颜色
     *
     * @param textColor 字体颜色
     */
    public void setTextColor(int textColor) {
        this.textColor = textColor;
        this.mTextPaint.setColor(textColor);
        invalidate();
    }

    /**
     * 设置角标方位,组合使用才有效果,比如 GRAVITY_LEFT|GRAVITY_TOP,表示居于左上角
     *
     * @param gravity 可选参数
     */
    public void setWarnGravity(int gravity) {
        if (gravity != this.gravity) {
            this.gravity = gravity;
            calculateRect();
            invalidate();
        }
    }

    public void setSkew(float skew) {
        this.mSkew = skew;
    }

    public float getSkew() {
        return mSkew;
    }
}
